package com.chiyoyo.tokenhelper;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.chiyoyo.redis.RedisUtil;
import lombok.NoArgsConstructor;
import org.springframework.util.Base64Utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import java.util.Base64;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

/**
 * TokenHelper 是一个用于处理令牌操作的工具类。
 * 它支持在基于 Redis 的系统中创建、验证和管理令牌。
 */
@NoArgsConstructor
public class TokenHelper {

    /**
     * token redis 前缀
     */
    private static final String PREFIX_TOKEN = "token::";

    /**
     * token支持多端登录
     */
    private static final String TOKEN_UNIQUE_MAP = "TOKEN_UNIQUE_INDEX";
    private static final String TOKEN_SALT = "YO.YO";

    /**
     * 单位：秒，72小时
     */
    public static final long TIMEOUT = 259200;
    private static RedisUtil redisUtil;

    @Resource
    public void setRedisUtil(RedisUtil redisUtil) {
        TokenHelper.redisUtil = redisUtil;
    }

    /**
     * 创建管理台登录token
     *
     * @param userid    用户ID
     * @param telephone 用户手机号
     * @return 登录的令牌
     */
    public static String createSysToken(String userid, String telephone) {
        return createToken(buildSysLoginInfo(userid, telephone, new HashSet<>()));
    }

    /**
     * 创建管理台登录token
     *
     * @param userid    用户ID
     * @param telephone 用户手机号
     * @param perms     用户权限集合
     * @return 登录的令牌
     */
    public static String createSysToken(String userid, String telephone, Set<String> perms) {
        return createToken(buildSysLoginInfo(userid, telephone, perms));
    }

    /**
     * 创建个人版app登录token
     *
     * @param userid    用户ID
     * @param telephone 用户手机号
     * @return 登录的令牌
     */
    public static String createAppToken(String userid, String telephone) {
        return createToken(buildAppLoginInfo(userid, telephone));
    }

    /**
     * 创建个人版app登录token，包含扩展参数
     *
     * @param userid       用户ID
     * @param telephone    用户手机号
     * @param extendParams 扩展参数
     * @return 登录的令牌
     */
    public static String createAppToken(String userid, String telephone, JSONObject extendParams) {
        LoginInfoDTO loginInfoDTO = buildAppLoginInfo(userid, telephone);
        loginInfoDTO.setExtendParams(extendParams);
        return createToken(loginInfoDTO);
    }

    /**
     * 创建管理台登录信息
     *
     * @param userid    用户ID
     * @param telephone 用户手机号
     * @param perms     用户权限集合
     * @return 登录信息
     */
    private static LoginInfoDTO buildSysLoginInfo(String userid, String telephone, Set<String> perms) {
        LoginInfoDTO loginInfoDTO = new LoginInfoDTO();
        loginInfoDTO.setUserType(UserType.MANAGER.getValue());
        loginInfoDTO.setUserid(userid);
        loginInfoDTO.setTelephone(telephone);
        loginInfoDTO.setPerms(perms);
        return loginInfoDTO;
    }

    /**
     * 创建个人版app登录信息
     *
     * @param userid    用户ID
     * @param telephone 用户手机号
     * @return 登录信息
     */
    private static LoginInfoDTO buildAppLoginInfo(String userid, String telephone) {
        LoginInfoDTO loginInfoDTO = new LoginInfoDTO();
        loginInfoDTO.setUserType(UserType.CONSUMER.getValue());
        loginInfoDTO.setUserid(userid);
        loginInfoDTO.setTelephone(telephone);
        return loginInfoDTO;
    }

    /**
     * 创建Token
     *
     * @param createTokenParam 创建Token的参数
     * @return 生成的Token
     */
    public static String createToken(LoginInfoDTO createTokenParam) {

        Integer userType = createTokenParam.getUserType();
        String userid = createTokenParam.getUserid();
        String useridEncode = Base64Utils.encodeToString(String.format("%s%s", userid, TOKEN_SALT).getBytes()).replaceAll("=", "");

        // 生成token
        String token = generateToken(userType, useridEncode);
        createTokenParam.setToken(token);
        String tokenKey = getTokenKey(token);
        redisUtil.set(tokenKey, JSON.toJSONString(createTokenParam), TIMEOUT);
        return token;
    }

    /**
     * 生成Token
     *
     * @param userType     用户类型
     * @param useridEncode 用户唯一值
     * @return 加密后的Token
     */
    private static String generateToken(Integer userType, String useridEncode) {

        byte[] bytes = new byte[16];
        new Random().nextBytes(bytes);
        String suffix = Base64.getEncoder().encodeToString(bytes).replaceAll("[\\s*\t\n\r]", "");
        return String.format("%d_%s_%s", userType, useridEncode, suffix);
    }

    /**
     * 获取登录信息
     *
     * @return 登录信息
     */
    public static LoginInfoDTO getLoginInfo() {
        //RequestContextHolder使用了ThreadLocal来存储请求对象
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (requestAttributes != null) {
            String token = requestAttributes.getRequest().getHeader("Authorization");
            return getLoginInfo(token);
        }
        return new LoginInfoDTO();
    }

    /**
     * 获取登录用户ID
     *
     * @return 用户ID
     */
    public static String getUserid() {
        LoginInfoDTO loginInfo = getLoginInfo();
        return loginInfo.getUserid() == null ? "0" : loginInfo.getUserid();
    }

    /**
     * 获取扩展字段中的数据
     *
     * @param extendParamKey 扩展字段的key
     * @return 扩展字段的值，如果找不到则返回null
     */
    public static String getExtendParam(String extendParamKey) {
        LoginInfoDTO loginInfo = getLoginInfo();
        JSONObject extendParams = loginInfo.getExtendParams();
        if (extendParams != null) {
            return extendParams.getString(extendParamKey);
        }
        return null;
    }

    /**
     * 根据Token获取登录信息
     *
     * @param token 登录的Token
     * @return 登录信息
     */
    public static LoginInfoDTO getLoginInfo(String token) {
        if (token == null || token.isEmpty()) {
            return new LoginInfoDTO();
        }
        String redisToken = redisUtil.get(getTokenKey(token));
        if (redisToken != null) {
            return JSON.parseObject(redisToken, LoginInfoDTO.class);
        }
        return new LoginInfoDTO();
    }

    /**
     * 获取日志用的登录信息
     *
     * @param token 登录的Token
     * @return JSON对象，包含Token和用户ID
     */
    public static JSONObject getLoginInfo2Log(String token) {
        JSONObject jsonObject = new JSONObject();
        if (token == null || token.isEmpty()) {
            return jsonObject;
        }
        LoginInfoDTO info = getLoginInfo(token);
        jsonObject.put("token", token.substring(token.lastIndexOf("_") + 1));
        if (info != null) {
            jsonObject.put("userid", info.getUserid());
        }
        return jsonObject;
    }

    /**
     * 检查Token是否有效
     *
     * @param token 登录的Token
     * @return 是否有效
     */
    public static boolean isLogin(String token) {
        if (token == null || token.isEmpty()) {
            return false;
        }
        String key = getTokenKey(token);
        if (!redisUtil.hasKey(key)) {
            return false;
        }
        // 获取登录信息
        LoginInfoDTO info = getLoginInfo(token);
        return token.equals(info.getToken());
    }

    /**
     * 刷新Token过期时间
     *
     * @param token 登录的Token
     */
    public static void refresh(String token) {
        redisUtil.expire(getTokenKey(token), TIMEOUT);
    }

    /**
     * 获取多端登录唯一索引
     *
     * @param userType 用户类型
     * @param officeId 机构ID
     * @param openid   用户唯一值
     * @return 多端登录唯一索引
     */
    private static String getUniqueIndex(Integer userType, Integer officeId, String openid) {
        return String.format("%d_%d_%s", userType, officeId, openid);
    }

    /**
     * 获取Redis存储的Token键
     *
     * @param token 登录的Token
     * @return Redis存储的Token键
     */
    public static String getTokenKey(String token) {
        if (token == null || token.isEmpty()) {
            return "";
        }
        return String.format("%s%s", PREFIX_TOKEN, token.substring(token.lastIndexOf("_") + 1));
    }

    /**
     * 删除Redis存储的登录信息
     *
     * @param redisKey Redis键
     */
    public static void deleteToken(String redisKey) {
        redisUtil.delete(redisKey);
    }
}
